package edu.ufl.cise.cop5555.sp12;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import edu.ufl.cise.cop5555.sp12.TokenStream;
import edu.ufl.cise.cop5555.sp12.TokenStream.Token;
import edu.ufl.cise.cop5555.sp12.TokenStream.TokenStreamIterator;
import edu.ufl.cise.cop5555.sp12.ast.*;

	public class Parser {
		Token t;
		TokenStream stream;
		TokenStreamIterator it;
		
		public Parser(TokenStream stream)
		{
			this.stream = stream;	
			it = stream.iterator();
			consume(); // Get first token in t
			
		}
		private void consume()
		{
			
			// get the next token from the TokenStream
			if(it.hasNext())
				t = it.next();
			else 
				t= null;
			
			
			
			
		}
		private Boolean isKind(Kind kind)
		{
			return kind == t.kind;
			
		}
		//Terminal symbols
		private void match(Kind a) throws SyntaxException
		{
			
			/*If current token is a, get next token from the stream , else error*/
			if (isKind(a)) {
				consume();
			} else
			{
				SyntaxException exp =  new SyntaxException(this.t, "Expected "+a+ " but got "+t.kind) ; 
				throw exp;
				
			}
			
			
		}
		public AST parse() throws SyntaxException{
	
			Program p = Program();  //method corresponding to start symbol
			return p;
	
		}
		
		// Program ::= prog IDENTIFIER <Block> gorp EOF
		private Program Program() throws SyntaxException{	
				if(isKind(Kind.PROG))
				{	
					Token t1= null;
					match(Kind.PROG);
					//match(Kind.IDENTIFIER);
					if(isKind(Kind.IDENTIFIER))
					{
						t1 = t;
						consume();
					}
					else
					{
						SyntaxException e = new SyntaxException(t, "Expected IDENTIFIER but got "+t.kind);
						throw e;
					}
					Block b=Block();
					match(Kind.GORP);
					match(Kind.EOF);
					Program p = new Program(t1,b);
					return p;
				}
				else
				{
					SyntaxException e = new SyntaxException(t, "Expected PROG but got "+t.kind);
					throw e;
					
				}
			}
			
		

		
		private Block Block() throws SyntaxException
		{
			//Implement while loop instead of recursion
			DecOrCommand decOrCommand = null;
            List<DecOrCommand> list =new ArrayList<DecOrCommand>();
			while(isKind(Kind.INT)|| isKind(Kind.BOOLEAN) || isKind(Kind.STRING) || isKind(Kind.MAP) || isKind(Kind.IDENTIFIER) || isKind(Kind.PRINT) || isKind(Kind.PRINTLN) || isKind(Kind.DO)|| isKind(Kind.IF) || isKind(Kind.SEMI) )
			{
				if(isKind(Kind.INT) || isKind(Kind.BOOLEAN) || isKind(Kind.STRING) || isKind(Kind.MAP))
				{

					decOrCommand  = Declaration();
					match(Kind.SEMI);
					
				}
				else if (isKind(Kind.IDENTIFIER) || isKind(Kind.PRINT) || isKind(Kind.PRINTLN) || isKind(Kind.DO)|| isKind(Kind.IF) || isKind(Kind.SEMI) ) 
				{
					//TODO add code for command
					decOrCommand = Command();
					match(Kind.SEMI);
					
				}
				if(decOrCommand != null)
					list.add(decOrCommand);
				
			}
			
			Block b = new Block(list);
			return b;

		
			
			
		}
		
		
		private Declaration Declaration() throws SyntaxException
		{
			Declaration d = null;
			if(isKind(Kind.INT) || isKind(Kind.BOOLEAN) || isKind(Kind.STRING) || isKind(Kind.MAP))
			{
			Token t1 = null;
			
			Type type= Type();
			
			if(isKind(Kind.IDENTIFIER))
			{
				t1 = t;
				consume();
			}
			else
			{
				SyntaxException e = new SyntaxException(t, "Expected IDENTIFIER but got "+t.kind);
				throw e;
			}
			//match(Kind.IDENTIFIER);
			 d = new Declaration(type, t1);
			
			}
			else
			{
				SyntaxException e = new SyntaxException(t, "Expected INT,BOOLEAN,STRING or MAP but got "+t.kind);
				throw e;
				
			}
			return d;
		}
		
		private Command Command() throws SyntaxException
		{
			Command cmd = null;
			if(isKind(Kind.IDENTIFIER))
			{			
				
				LValue l = Lvalue();
				match(Kind.ASSIGN);
				if(isKind(Kind.IDENTIFIER) || isKind(Kind.INTEGER_LITERAL )|| isKind(Kind.BOOLEAN_LITERAL) || isKind(Kind.STRING_LITERAL) || isKind(Kind.LEFT_PAREN) || isKind(Kind.NOT) || isKind(Kind.MINUS) ) 
				{
					//AssignExpr command
					 Expression e =Expression();
					 cmd = new AssignExprCommand(l, e);
					
					
				}
				else if (isKind(Kind.LEFT_BRACE))
				{
					//AssignPairList cmd
					PairList pL = PairList();
					cmd =  new AssignPairListCommand(l, pL);
					
				}
				else
				{
					SyntaxException e = new SyntaxException(t, "Error :Found "+t.kind);
					throw e;
				}
			
			}
			else if (isKind(Kind.PRINT))
			{
				//Println command
				match(Kind.PRINT);
				Expression e = Expression();
				cmd = new PrintCommand(e);
				
				
			}
			else if (isKind(Kind.PRINTLN))
			{
				//Print command
				match(Kind.PRINTLN);
				Expression e = Expression();
				cmd = new PrintlnCommand(e);
				
				
			}
			else if (isKind(Kind.DO))
			{
				//Do command
				match(Kind.DO);
				//CmdTail2();
				if(isKind(Kind.LEFT_PAREN))
				{
					
					match(Kind.LEFT_PAREN);
					Expression e = Expression();
					match(Kind.RIGHT_PAREN);
					Block b =Block();
					match(Kind.OD);
					cmd = new DoCommand(e,b);
					
					
				}
				else if(isKind(Kind.IDENTIFIER))
				{
					
						
						LValue lv = Lvalue();
						Token t1= null;
						Token t2 = null;
						match(Kind.COLON);
						match(Kind.LEFT_SQUARE);
						if(isKind(Kind.IDENTIFIER))
							{
							t1 = t;
							consume();
							}
						else
						{
							SyntaxException e = new SyntaxException(t, "Expected IDENTIFIER but got "+t.kind);
							throw e;
						}
						//match(Kind.IDENTIFIER);
						match(Kind.COMMA);
						if(isKind(Kind.IDENTIFIER))
						{
						t2 = t;
						consume();
						}
						else
						{
							SyntaxException e = new SyntaxException(t, "Expected IDENTIFIER but got "+t.kind);
							throw e;
						}
						
						//match(Kind.IDENTIFIER);
						match(Kind.RIGHT_SQUARE);
						Block b = Block();
						match(Kind.OD);
						cmd = new DoEachCommand(lv, t1, t2, b);
					
			
						
				}
				else 
				{
					SyntaxException e = new SyntaxException(t, "Error: Found "+t.kind);
					throw e;
					
				}
			
				
			}
			else if(isKind(Kind.IF))
			{
				//If command , If else command
	
				match(Kind.IF);
				match(Kind.LEFT_PAREN);
				Expression e = Expression();
				match(Kind.RIGHT_PAREN);
				Block ifBlock = Block();
				//CmdTail3();
				if(isKind(Kind.FI))
				{
					match(Kind.FI);
					cmd = new IfCommand(e, ifBlock);
					
					
				}
				else if(isKind(Kind.ELSE))
				{
					match(Kind.ELSE);
					Block elseBlock = Block();
					match(Kind.FI);
					cmd = new IfElseCommand(e, ifBlock, elseBlock);
				
				}
				else
				{
					SyntaxException exp = new SyntaxException( t, "Error : Found "+t.kind);
					throw exp;
				}
				
			}
			
			else 
			{
				//return;
			}
		return cmd;	
		}
		/*
		private void  CmdTail1() throws SyntaxException
		{
		
			
			
		}
		*/
		private  LValue Lvalue()throws SyntaxException
		{
			Token t1 = null;
			LValue lval = null;
			
			if(isKind(Kind.IDENTIFIER))
			{
				
				
				if(isKind(Kind.IDENTIFIER))
				{
					t1 = t;
					consume();
					
				}
				else
				{
					SyntaxException exp = new SyntaxException( t, "Unknown token "+t.kind+" in CmdTail3");
					throw exp;
					//error
				}
			//	match(Kind.IDENTIFIER);
			//	LvalueTail();
			
				if(isKind(Kind.LEFT_SQUARE))
				{
					
					match(Kind.LEFT_SQUARE);
					Expression e = Expression();
					match(Kind.RIGHT_SQUARE);
					lval = new ExprLValue(t1, e);
					
				}
				else
				{
					
					lval = new SimpleLValue(t1);
				}
				
			}
			else
			{
				SyntaxException e = new  SyntaxException(t, "Expected IDENTIFIER but got "+t.kind);
				throw e;
				
			}
			return lval;
			
			
		}
		

		private Expression Expression() throws SyntaxException{
			Expression exp0 = null;
			Expression exp1 = null;
			
			if(isKind(Kind.IDENTIFIER) || isKind(Kind.INTEGER_LITERAL )|| isKind(Kind.BOOLEAN_LITERAL) || isKind(Kind.STRING_LITERAL) || isKind(Kind.LEFT_PAREN) || isKind(Kind.NOT) || isKind(Kind.MINUS) || isKind(Kind.LEFT_BRACE)) 
			{	
			
				exp0 = Term();
		
				while(isKind(Kind.OR) || isKind(Kind.AND)|| isKind(Kind.EQUALS) || isKind(Kind.NOT_EQUALS) || isKind(Kind.LESS_THAN) || isKind(Kind.GREATER_THAN)|| isKind(Kind.AT_MOST)|| isKind(Kind.AT_LEAST))
				{
					Kind op;
					op = RelOp();
					exp1 = Term();
					exp0 = new BinaryOpExpression(exp0, op, exp1);
				
				}
				return exp0;
			
			}
			else
			{
				SyntaxException e = new SyntaxException(t, "Expected an Expression but got "+t.kind);
				throw e;
			}
			
			
			
			
		}
		
		private Kind RelOp() throws SyntaxException {
			Kind op = null;
			if(isKind(Kind.OR))
			{
				op = Kind.OR;
				consume();
				//match(Kind.OR);
			}
			else if(isKind(Kind.AND))
			{	//match(Kind.AND);
				op = Kind.AND;
				consume();
			}
			else if(isKind(Kind.EQUALS))
			{	
				//match(Kind.EQUALS);
				op = Kind.EQUALS;
				consume();
			}
			else if(isKind(Kind.NOT_EQUALS))
			{	//match(Kind.NOT_EQUALS);
				op = Kind.NOT_EQUALS;
				consume();
			
			}
			else if(isKind(Kind.LESS_THAN))
			{	//match(Kind.LESS_THAN);
				op = Kind.LESS_THAN;
				consume();
			}
			else if(isKind(Kind.GREATER_THAN))
			{//	match(Kind.GREATER_THAN);
				op = Kind.GREATER_THAN;
				consume();
			}
			else if(isKind(Kind.AT_MOST))
			{	//match(Kind.AT_MOST);
				op  = Kind.AT_MOST;
				consume();
			}
			else if(isKind(Kind.AT_LEAST))
			{	//match(Kind.AT_LEAST);
				op = Kind.AT_LEAST;
				consume();
			}
			else
			{
				SyntaxException e = new SyntaxException(t, "Expected relational operator but got "+t.kind);
				throw e;
			}
			return op;
		}
		
		private Expression Term() throws SyntaxException
		{
			Expression exp0 = null;
			Expression exp1 = null;
			if(isKind(Kind.IDENTIFIER) || isKind(Kind.INTEGER_LITERAL )|| isKind(Kind.BOOLEAN_LITERAL) || isKind(Kind.STRING_LITERAL) || isKind(Kind.LEFT_PAREN) || isKind(Kind.NOT) || isKind(Kind.MINUS))
			{
			
					exp0 = Elem();
					while(isKind(Kind.PLUS) || isKind(Kind.MINUS))
					{
						Kind op = WeakOp();
						exp1 = Elem();
						exp0 = new BinaryOpExpression(exp0, op, exp1);
					}
				return exp0;
			}
			else
			{
				SyntaxException e = new SyntaxException(t, "Expected term but got "+t.kind);
				throw e;
			}
		}
		
		
		private Kind WeakOp() throws SyntaxException
		{
			Kind op ;
			if(isKind(Kind.PLUS))
				{//match(Kind.PLUS);
				op = Kind.PLUS;
				consume();
				
				}
			else if (isKind(Kind.MINUS))
				{//match(Kind.MINUS);
				op = Kind.MINUS;
				consume();
				
				
				}
			else
			{
				SyntaxException e = new SyntaxException(t, "Expected PLUS or MINUS but got "+t.kind);
				throw e;
			}
			return op;
		}
		
		private Kind StrongOp() throws SyntaxException{
			
			Kind op;
			if(isKind(Kind.TIMES))
				{//match(Kind.TIMES);
				op  = Kind.TIMES;
				consume();
				}
			else if (isKind(Kind.DIVIDE))
				{//match(Kind.DIVIDE);
				op = Kind.DIVIDE;
				consume();
				}
			else
			{
				SyntaxException e = new SyntaxException(t, "Expected TIMES or DIVIDE but got "+t.kind);
				throw e;
			}
			return op;
			
		}
		
		private Expression Elem() throws SyntaxException
		{
			Expression exp0 = null;
			Expression exp1=null;
			if(isKind(Kind.IDENTIFIER) || isKind(Kind.INTEGER_LITERAL )|| isKind(Kind.BOOLEAN_LITERAL) || isKind(Kind.STRING_LITERAL) || isKind(Kind.LEFT_PAREN) || isKind(Kind.NOT) || isKind(Kind.MINUS) ) 
			{
				
				
					exp0 = Factor();
					while(isKind(Kind.TIMES) || isKind(Kind.DIVIDE))
					{
						Kind op = StrongOp();
						exp1 = Factor();
						exp0 = new BinaryOpExpression(exp0, op, exp1);
					}
				return exp0;	
				

			}
			else
			{
				SyntaxException e = new SyntaxException(t, "Expected Elem but got "+t.kind);
				throw e;
			}
			
		}
	
		private Expression Factor() throws SyntaxException
		{
			Expression exp = null;
		if(isKind(Kind.IDENTIFIER))
		{
			LValue lval = Lvalue();
			exp = new LValueExpression(lval);
			
		}
		else if(isKind(Kind.INTEGER_LITERAL))
			{
			
			Token int_lit = t;
			exp = new IntegerLiteralExpression(int_lit);
			consume();
			//match(Kind.INTEGER_LITERAL);
			
			}
		else if (isKind(Kind.BOOLEAN_LITERAL))
			{//match(Kind.BOOLEAN_LITERAL);
			Token bool_lit = t;
			exp = new BooleanLiteralExpression(bool_lit);
			consume();
			
			}
		else if(isKind(Kind.STRING_LITERAL))
			{//match(Kind.STRING_LITERAL);
			Token str_lit = t;
			exp = new StringLiteralExpression(str_lit);
			consume();
			
			}
		else if(isKind(Kind.LEFT_PAREN))
		{
			
			match(Kind.LEFT_PAREN);
			exp = Expression();
			match(Kind.RIGHT_PAREN);
			
			
		}
		else if(isKind(Kind.NOT))
		{
			match(Kind.NOT);
			Kind op = Kind.NOT;
			Expression e0 = Factor();
			exp = new UnaryOpExpression(op, e0);
		}
		else if(isKind(Kind.MINUS))
		{
			match(Kind.MINUS);
			Kind op = Kind.MINUS;
			Expression e0 = Factor();
			exp = new UnaryOpExpression(op, e0);
		}
		else 
		{
			SyntaxException e = new SyntaxException(t, "Expected Factor but got "+t.kind);
			throw e;
		}
		return exp;
		}
	
	/*
		private void CmdTail2() throws SyntaxException
		{
			if(isKind(Kind.LEFT_PAREN))
			{
				
				match(Kind.LEFT_PAREN);
				Expression();
				match(Kind.RIGHT_PAREN);
				Block();
				match(Kind.OD);
				
			}
			else if(isKind(Kind.IDENTIFIER))
			{
				
					
					Lvalue();
					match(Kind.COLON);
					match(Kind.LEFT_SQUARE);
					match(Kind.IDENTIFIER);
					match(Kind.COMMA);
					match(Kind.IDENTIFIER);
					match(Kind.RIGHT_SQUARE);
					Block();
					match(Kind.OD);
				
			}
			else 
			{
				SyntaxException e = new SyntaxException(t, "Unknown token "+t.kind+" in CmdTail2");
				throw e;
				
			}
			
			
		}
		private void CmdTail3() throws SyntaxException
		{
			if(isKind(Kind.FI))
			{
				match(Kind.FI);
				
			}
			else if(isKind(Kind.ELSE))
			{
				match(Kind.ELSE);
				Block();
				match(Kind.FI);
			}
			else
			{
				SyntaxException e = new SyntaxException( t, "Unknown token "+t.kind+" in CmdTail3");
				throw e;
			}
			
			
		}
		*/
		
		private PairList PairList() throws SyntaxException 
		{
			PairList pairList = null;
			List<Pair> list = new ArrayList<Pair>();
			if(isKind(Kind.LEFT_BRACE))
				{
				match(Kind.LEFT_BRACE);
				//PairListTail();
				if(isKind(Kind.RIGHT_BRACE))
				{
					match(Kind.RIGHT_BRACE);
					
				}
				else if(isKind(Kind.LEFT_SQUARE))
				{
					Pair p = Pair();
					list.add(p);
					while(isKind(Kind.COMMA))
					{
						match(Kind.COMMA);
						Pair p1 = Pair();
						list.add(p1);
					}
					match(Kind.RIGHT_BRACE);
				}
				else
				{
					SyntaxException e = new SyntaxException( t, "Expected RIGHT_BRACE or LEFT_SQUARE but got "+t.kind);
					throw e;
					
				}
			
				}
			else
			{
				SyntaxException e = new SyntaxException( t, "Expected LEFT_BRACE but got "+t.kind);
				throw e;
			}
				
			pairList = new PairList(list);
			return pairList;
		}
		/*private void PairListTail() throws SyntaxException
		{
			
			
		}*/
		
		private Pair Pair() throws SyntaxException
		{
			Expression e0 = null;
			Expression e1= null;
			if(isKind(Kind.LEFT_SQUARE))
			{
				match(Kind.LEFT_SQUARE);
				e0 = Expression();
				match(Kind.COMMA);
				e1 = Expression();
				match(Kind.RIGHT_SQUARE);
				Pair p = new Pair(e0, e1);
				return p;
				
			}
			else 
			{
				SyntaxException e = new SyntaxException( t, "Expected LEFT_SQUARE but got "+t.kind);
				throw e;
			}
		}
	
		private Type Type() throws SyntaxException
		{
			Type type = null;
			if(isKind(Kind.INT) || isKind(Kind.BOOLEAN) || isKind(Kind.STRING))
			{
				//Simple
				
				type = SimpleType();
				
			}
			else if (isKind(Kind.MAP))
			{
				
				
				type = CompoundType();
				
				
				
			}
			else 
			{
				//error;
				SyntaxException e = new SyntaxException(t, "Expected Type but got "+t.kind);
				throw e;
			}
			return type;
		}
		
		private SimpleType SimpleType()throws SyntaxException
		{
			SimpleType type = null;
			if(isKind(Kind.INT) )
				{//match(Kind.INT);
				Kind op = Kind.INT;
				type = new SimpleType(op);
				consume();
				}
			else if(isKind(Kind.BOOLEAN) )
				{//match(Kind.BOOLEAN);
				
				Kind op = Kind.BOOLEAN;
				type = new SimpleType(op);
				consume();
				}
			else if(isKind(Kind.STRING) )
				{//match(Kind.STRING);
				Kind op = Kind.STRING;
				type = new SimpleType(op);
				consume();
				
				}
			else {
				//error
				SyntaxException e = new SyntaxException(t, "Expected SimpleType but got "+t.kind);
				throw e;
			}
				
			return type;
			
		}
	
		private CompoundType CompoundType()throws SyntaxException
		{
			CompoundType type = null;
			
			if(isKind(Kind.MAP))
			{
				match(Kind.MAP);
				match(Kind.LEFT_SQUARE);
				SimpleType keyType = SimpleType();
				match(Kind.COMMA);
				Type valType = Type();
				match(Kind.RIGHT_SQUARE);
				type = new CompoundType(keyType, valType);
				
			}
			else
			{
				//error;
				SyntaxException e = new SyntaxException(t, "Expected CompoundType but got "+t.kind);
				throw e;
			}
			return type;
			
		}
	}
	